﻿namespace Zaggit.Core
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using Zaggit.DomainObjects;

    /// <summary>
    /// Provides the ability to send and receieve messages across a network using UDP.
    /// </summary>
    public class Messager : IDisposable, IMessager
    {

        #region Fields

        private readonly ICrypto _Crypto;

        private readonly UTF32Encoding _Encoding;

        private readonly IPEndPoint _EndPoint;

        private readonly Timer _KeepAlive;

        private readonly Timer _MessageChecker;

        private readonly int _Port;

        private IPEndPoint _RemoteEp;

        private readonly UdpClient _UdpClient;

        private readonly UdpClient _UdpReceiver;

        #endregion

        #region Events

        /// <summary>
        /// Occurs when [message recieved].
        /// </summary>
        public event Action<UserMessage> MessageRecieved;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the Zaggit class.
        /// </summary>
        /// <param name="port">The port.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="crypto">The crypto.</param>
        public Messager(int port, UTF32Encoding encoding, ICrypto crypto)
        {
            _Encoding = encoding;
            _Crypto = crypto;
            _UdpClient = new UdpClient() { EnableBroadcast = true };
            _UdpClient.AllowNatTraversal(true);
            var ip = IPAddress.Broadcast;
            _Port = port;
            _EndPoint = new IPEndPoint(ip, Port);
            _RemoteEp = new IPEndPoint(IPAddress.Any, this._Port);
            _UdpReceiver = new UdpClient(_RemoteEp);
            _MessageChecker = new Timer(x => ListenForMessages(), null, 200, 150);
            _KeepAlive = new Timer(x => KeepAlive(), null, 200, 2000);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the port which the messager listens on.
        /// </summary>
        /// <value>
        /// The port.
        /// </value>
        public int Port
        {
            get
            {
                return this._Port;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Sends the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>The number of bytes transmitted.</returns>
        public int Send(UserMessage message)
        {
            var messageString = string.Format("{0}|{1}|{2}|{3}", message.Recipient, (int)message.Type, message.Contents, message.Sender);
            if (messageString.Count(c => c == '|') != 3) return 0;

            var messageBytes = this._Encoding.GetBytes(messageString);
            var encryptedMessage = this._Crypto.Encrypt(messageBytes);
            return _UdpClient.Send(encryptedMessage, encryptedMessage.Length, _EndPoint);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this._UdpClient.Close();
                this._MessageChecker.Dispose();
                this._KeepAlive.Dispose();
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Finalizes an instance of the <see cref="Messager" /> class.
        /// </summary>
        ~Messager()
        {
            Dispose(false);
        }

        /// <summary>
        /// Keeps the system alive by sending a keep-alive pulse.
        /// </summary>
        private void KeepAlive()
        {
            var message = new UserMessage { Contents = string.Empty, Sender = Environment.UserName, Type = MessageType.KeepAlive };
            this.Send(message);
        }

        /// <summary>
        /// Listens for messages.
        /// </summary>
        private void ListenForMessages()
        {
            if (_UdpReceiver.Available > 0)
            {
                var receivedBytes = this._UdpReceiver.Receive(ref _RemoteEp);
                var decryptedMessage = this._Crypto.Decrypt(receivedBytes);
                var messageString = this._Encoding.GetString(decryptedMessage);
                if (messageString.Count(c => c == '|') != 3) return;    
                var messageComponents = messageString.Split('|');
                var messageType = (MessageType)int.Parse(messageComponents[1]);
                var recipient = messageComponents[0];
                if (messageType == MessageType.Private && recipient != Environment.UserName) return;

                var contents = messageComponents[2];
                var sender = messageComponents[3];
                var message = new UserMessage { Recipient = recipient, Type = messageType, Contents = contents, Sender = sender };
                MessageRecieved(message);
            }
        }

        #endregion

    }
}
